# IBM_PROLOG_BEGIN_TAG 
# This is an automatically generated prolog. 
#  
#  
#  
# Licensed Materials - Property of IBM 
#  
# (C) COPYRIGHT International Business Machines Corp. 1997,2002 
# All Rights Reserved 
#  
# US Government Users Restricted Rights - Use, duplication or 
# disclosure restricted by GSA ADP Schedule Contract with IBM Corp. 
#  
# IBM_PROLOG_END_TAG 

# @(#)59   1.18	 src/rsct/pgs/cmds/hagsreap.pl, gsdaemon, rsct_rori, roris01a 5/13/02 18:31:59

#######################################################################
#  hagsreap - cleanup hags core and log files 
#
#  hagsreap DaemonName Partition (Erase|(P|p)[rint]) [ SizeLimit [ NodeNumber [ LogDir [ CoreDir ] ] ] ]
#
#  DaemonName and Partition are required, and specify which daemon to clean up after.
#  SizeLimit defaults to 5 MB, and is the cap in bytes.
#  If NodeNumber is not specified, use HA_DOMAIN_TYPE to determine how to get it.
#  LogDir defaults to /var/ha/log, and is the directory where the log files are kept and cleaned up.
#  CoreDir defaults to LogDir if it's specified,
#                   or /var/ha/run/hags.Partition otherwise
#
#  exit value is the number of (core and log) files removed
#  (note that therefore the "error" return value is 0!)
#######################################################################

print "$0 input arguments: ", join(' ',@ARGV), "\n";

unshift(@INC, '/usr/sbin/rsct/bin');

#require "getopts.pl";

($dir,$progname) = $0 =~ /(.*\/)?(.*)/; # get basename of $0

sub Usage {
    warn "Usage: $progname [ -d DefaultLogName ] DaemonName Partition (Erase|(P|p)[rint]) [ SizeLimit [ NodeNumber [ LogDir [ CoreDir ] ] ] ]\n";
    exit -1;
}

# Avoid getopts, to remove dependencies on Perl libraries...
#if ( 1 != &Getopts( "d:" ) ) {
#    &Usage;
#}
#$DefaultLogName = $opt_d;

if ("-d" eq @ARGV[0]) {
    shift @ARGV;
    $DefaultLogName = shift @ARGV;
}

#
# set locale to "C" so that "ls" output should be locale-free
$ENV{'LC_ALL'} = "C";

($Daemon_Name, $Partition, $EraseOrPrint, $SizeLimit, $Node_Number, $LogDir, $CoreDir) = @ARGV;

$EraseTheFiles = 0; # Operational flag for Erase/Print parameter

if ("" eq $EraseOrPrint) {
	warn "Not enough arguments";
	&Usage;
} else {
	if ("Erase" eq $EraseOrPrint) { #OK!
		$EraseTheFiles = 1;
	} else {
		if ($EraseOrPrint =~ /^[Pp](.*)/) {
			$rest = $1;
			if ( ("" ne $rest) && (index("rint", $rest) != $[) ) {
				warn "Print flag invalid";
				&Usage;
			}
		} else {
			warn "Erase|Print flag invalid";
			&Usage;
		}
	}
}

if ("" eq $SizeLimit) {
	$SizeLimit = 5 * 1024 * 1024; # 5 MB default size limit
} else {
	if ($SizeLimit !~ /^[0-9]+$/) {
		warn "SizeLimit must be numeric";
		&Usage;
	}
}
if ("" eq $Node_Number) {
    if (!defined( $ENV{'HA_DOMAIN_TYPE'} )) {
        die "Node number not given, and HA_DOMAIN_TYPE not set.  Exit.\n";
    } else {
        $domainType = $ENV{'HA_DOMAIN_TYPE'};
        if ($domainType eq "HAES") {
            if (!defined( $ENV{'HB_SERVER_SOCKET'} ) ) {
                $ENV{'HB_SERVER_SOCKET'} = "/var/ha/soc/topsvcs/server_socket";
            }
            $Node_Number=`hats_node_number -d $Partition`;
            while (0 != $?) {
                sleep(2);
                $Node_Number=`hats_node_number -d $Partition`;
            }
	} elsif($domainType eq "CLUSTER") {
	    if(!defined($ENV{'HB_SERVER_SOCKET'})) {
		warn "HB_SERVER_SOCKET is not defined";
        	&Usage;
	    }
            $Node_Number=`hats_node_number -d $Partition`;
            while (0 != $?) {
                sleep(2);
                $Node_Number=`hats_node_number -d $Partition`;
            }
        } else {
            $Node_Number=`/usr/lpp/ssp/install/bin/node_number`;
        }
        chop($Node_Number);
    }
}
if ($Node_Number !~ /^[0-9]+$/) {
	warn "NodeNumber must be numeric";
	&Usage;
}

if ("" eq $LogDir) {
	if($domainType eq "CLUSTER") {
                warn "LogDir is not provided";
                &Usage;
	}
	$LogDir = "/var/ha/log";
	$CoreDir = "/var/ha/run/$Daemon_Name.$Partition";
}
if ("" eq $CoreDir) {
        if($domainType eq "CLUSTER") {
                warn "CoreDir is not provided";
                &Usage;
        }
	$CoreDir = $LogDir;
}

$flag = (1 == $EraseTheFiles) ? "Erase" : "Print";

print "$progname parsed arguments: $Daemon_Name $Partition $flag $SizeLimit $Node_Number $LogDir $CoreDir\n";

sub piperc {
	local($rc) = (256 <= $_[0]) ? $_[0]/256 : $_[0];
	local($cmd) = $_[1];
	local($line) = $_[2];

	if ( 0 != $rc ) {
		warn "\n$!\n";
		warn "$0: $cmd at line $line failed: rc = $rc.\n";
		exit 0;
	}
}

$NaN = 65535;  # Not a Number

# Get all core and log files
# Determine and save their Incarnation Number, name and size

if ( $LogDir eq $CoreDir ) {
	$dirs = $LogDir;
} else {
	$dirs = "$LogDir $CoreDir";
}
$cmd = "ls -lL $dirs |";
# print "$cmd\n";
if ( ! open(FILES, $cmd ) ) {
	$rc = $! + 0;
	warn "\n$!\n";
	warn "Can't get any log and core files";
	exit 0;
}
if (0 == $Node_Number) {
	$Daemon_Name = "$Daemon_Name.$Partition";
}
$CorePattern = "core_$Node_Number\.([0-9]+)";
$BadCorePattern = "core_([0-9]+)\.([0-9]+)";
$LogPattern = "$Daemon_Name"."_$Node_Number"."_([0-9]+)\\.$Partition";
$LongLogPattern = "$Daemon_Name"."_$Node_Number"."_([0-9]+)\\.$Partition\.long";
$BadLogPattern = "$Daemon_Name"."_([0-9]+)"."_([0-9]+)\\.$Partition";
while(<FILES>) {
    ($perms, $links, $owner, $group, $size, $month, $day, $time, $name) = split(' ');
    $name =~ s/.*\///o; # get basename of file name; strip off /s and dirname
    if ( ($name =~ /^core_$Node_Number\.([0-9]+)$/o) && ($NaN > $1) ) {
        $incarnationNumber = $1;
        # print "$name, $incarnationNumber\n";
        $CoreFileName{$incarnationNumber} = $name;
        $CoreFileSize{$incarnationNumber} = $size;
    } elsif ( ($name =~ /^$LogPattern(\.bak)?$/o) && ($NaN > $1) ) {
        $incarnationNumber = $1;
        $bak = $2;
        if ("" eq $bak) { # a regular log file
            # print "$name, $incarnationNumber\n";
            $LogFileName{$incarnationNumber} = $name;
            $LogFileSize{$incarnationNumber} = $size;
        } else { # a log.bak file
            # print "$name, $incarnationNumber\n";
            $BakFileName{$incarnationNumber} = $name;
            $BakFileSize{$incarnationNumber} = $size;
        }
    } elsif ( ($name =~ /^$LongLogPattern(\.bak)?$/o) && ($NaN > $1) ) {
        $incarnationNumber = $1;
        $bak = $2;
        if ("" eq $bak) { # a regular long log file
            # print "$name, $incarnationNumber\n";
            $LongLogFileName{$incarnationNumber} = $name;
            $LogFileSize{$incarnationNumber} = $size;
        } else { # a long log.bak file
            # print "$name, $incarnationNumber\n";
            $LongBakFileName{$incarnationNumber} = $name;
            $BakFileSize{$incarnationNumber} = $size;
        }
    } elsif (($name =~ /^$BadLogPattern(\.bak)?$/o) && ($NaN > $1) ) {
        # A common situation we have seen is that a mksysb contains log
        # and core files from a node.  When this is installed on a new
        # node, the node_number value does not match the current node
        # number, and hagsreap does not erase these files, and they just
        # sit taking up space in /var/ha/.  Therefore, we assume the
        # first two tests will find the log & core files for THIS node,
        # we only get to these latter tests for files that follow the
        # hags pattern, but do NOT match on node number.  Try to catch
        # those here, and simply erase them.
        print "Bad log file line $.  Will erase it: $LogDir/$name, $_";
        $cmd = "rm -f $LogDir/$name";
        system($cmd);
    } elsif ( ($name =~ /^core_([0-9]+)\.([0-9]+)$/o) && ($NaN > $1) ) {
        print "Bad core file line $.  Will erase it: $CoreDir/$name, $_";
        $cmd = "rm -f $CoreDir/$name";
        system($cmd);            
    } else {
        # print "Bad FILES line $.: $name, $_";
        next;
    }
}
close FILES; # this causes a wait on pipe child process, with $? set to its rc
&piperc( $?, $cmd, __LINE__ );

if ("" ne $DefaultLogName) {
# get dirname of file name; strip off /s and dirname
($DefaultLogDir) = $DefaultLogName =~ /(.*)\/[^\/]+$/;
print "\$DefaultLogDir=$DefaultLogDir\n";
$cmd = "ls -lL ${DefaultLogName}* |";
# print "$cmd\n";
if ( ! open(FILES, $cmd ) ) {
	$rc = $! + 0;
	warn "\n$!\n";
	warn "Can't get any log and core files";
	exit 0;
}
while(<FILES>) {
    ($perms, $links, $owner, $group, $size, $month, $day, $time, $name) = split(' ');
    if ( ($name =~ /^$DefaultLogName.${Node_Number}_([0-9]+)$/o) && ($NaN > $1) ) {
    	$incarnationNumber = $1;
        # print "$name, $incarnationNumber\n";
        $name =~ s/.*\///o; # get basename of file name; strip off /s and dirname
        $DefLFileName{$incarnationNumber} = $name;
        $DefLFileSize{$incarnationNumber} = $size;
    } elsif ( $name ne $DefaultLogName ) {
        print "Bad FILE line $.  Will erase it: $name, $_";
        $cmd = "rm -f $name";
        system($cmd);
        next;
    }
}
close FILES; # this causes a wait on pipe child process, with $? set to its rc
&piperc( $?, $cmd, __LINE__ );
}

# foreach $incar (keys(%CoreFileName)) {
#	print "$CoreFileName{$incar}, $incar, $CoreFileSize{$incar}\n";
# }

# There are three things to potentially save for each incarnation number:
# 1. the log
# 2. the core file
# 3. the log.bak file, or backup log, if it has wrapped
# The order listed in the priority order

# Our approach:
# 1. Establish the weight or priority of each incarnation number.
#    The weight of each incarnation number is it's position in the
#    sorted list, except that the priority of incarnation numbers
#    with core files is raised.
# 2. Sort the incarnation numbers by weight.
# 3. Save the files from the top incarnation numbers that fit under the SizeLimit.

# Save an incarnation number and it's weight for subsequent sorting by weight
sub AddRecord {
	local ($incar) = $_[0];
	local ($weight) = $_[1];
	local ($record) = "$weight $incar";
#	printf "AddRecord: %9.2f %10d\n", split(' ',$record);
	push(@Files, $record);
}

# taken from SequenceNum.[Ch].

$NaN = 65535;  # Not a Number
$kHalfSequence = 32767;

# note we've insured that all Incarnation Numbers are < $NaN

sub ByIncarnationNumber {
	if ($a == $b) { return 0; } # equal
#	if ($NaN == $a) { return 1; } # a > b
#	if ($NaN == $b) { return -1; } # a < b
	local($c) = ($a + $NaN -1 - $b) % $NaN;
	return ($kHalfSequence > $c) ? -1 : 1;
}

# Establish incarnation number weights by sorting by incarnation number,
# and adjusting the weights of those with core files.

$factor = 65537./137623.; # two primes, $factor =~ 1/2.1. 65537 = smallest prime > $NaN
# I do this so that no two incarnation numbers should ever have the same weight
$NumIncars = 0; # number of incarnation numbers seen
$LastIncarSeen = $NaN; # beyond the valid range
$LargestIncar = 0;
$LargestCoreIncar = 0;
if( $Daemon_Name=~/glsm/ ){
    $NumIncarToKeep = 2;
} else {
    $NumIncarToKeep = 3;
}

foreach $incarnationNumber (sort ByIncarnationNumber (keys(%CoreFileName), keys(%LogFileName), keys(%BakFileName), keys(%LongLogFileName), keys(%LongBakFileName), keys(%DefLFileName))) {
	if ($LastIncarSeen == $incarnationNumber) {
		next; # skip duplicate incarnation numbers
	}
        if( $LargestIncar < $incarnationNumber ){
            $LargestIncar = $incarnationNumber;
        }
	# new incarnation number
	$LastIncarSeen = $incarnationNumber;
	$weight = $NumIncars++; # it's sorted order
        if( $NumIncars <= $NumIncarToKeep ){
            $weight *= $factor;
        }
	if ( defined($CoreFileName{$incarnationNumber}) ) {
		# this incar has a core file; adjust weight
		$weight *= $factor;
                if( $LargestCoreIncar < $incarnationNumber ){
                    $LargestCoreIncar = $incarnationNumber;
                }
	}
	&AddRecord( $incarnationNumber, $weight );
}
print "largest incar = $LargestIncar,LargestCoreIncar = $LargestCoreIncar\n";

# foreach $record (@Files) {
	# printf "%9.2f %10d %-s\n", split(' ',$record);
# }

# OK, now that we have the weights, sort by it!

sub ByWeight {
	local($aweight, $arest, $bweight, $brest);
	($aweight, $arest) = split(' ', $a);
	($bweight, $brest) = split(' ', $b);
	$aweight <=> $bweight;
}

print "SizeLimit = $SizeLimit\n";

$CumSize = 0;
$NumTrash = 0;
$NumKeep = 0;
$n = 0;

# If we are under SizeLimit and the next incarnation number has a core file,
# keep the core file and both logs, even if we exceed SizeLimit.
# Otherwise, only keep a log if doing so will keep us under SizeLimit.

if( !defined( $ENV{'PGSD_MAX_DAYS_TO_KEEP_COREFILE'} ) ){
    print "PGSD_MAX_DAYS_TO_KEEP_COREFILE is not set.\n";
    $MaxDays = 7;
} else {
    $MaxDays = $ENV{'PGSD_MAX_DAYS_TO_KEEP_COREFILE'};
}
sub ProcessFile {
	local ($name, $size, $HasaCoreFile, $dir, $incarNum) = @_;

	$n++;
	if ( 0 == $NumTrash ) {
            if( $LargestIncar - $incarNum >= $NumIncarToKeep){
                if( !HasaCoreFile ){
			print "Size limit exceeded! Keep those $NumKeep, trash the rest of these!\n";
			$NumTrash = 1;
			$BytesKept = $CumSize;
		} elsif( $incarNum != $LargestCoreIncar ) {
                        $NumTrash =1;
			$BytesKept = $CumSize;
                } else{
                        @a = stat("$CoreDir/$CoreFileName{$incarNum}");
                        local($lastTime) = $a[9];
                        local($currentTime) = time();
                        local($timeDiff) = 1+int(($currentTime - $lastTime)/(3600*24));
                        if( $timeDiff > $MaxDays ){ 
                            $NumTrash=1;
                            $BytesKept = $CumSize;
                        }
                }
            } elsif( $name eq $CoreFileName{$incarNum} ){
		    if( $incarNum != $LargestCoreIncar ) {
         		$cmd = "rm -f  $CoreDir/$CoreFileName{$incarNum}";
	        	print "$cmd\n";
	        	if (1 == $EraseTheFiles) {
	        		system($cmd);
	        	}
			$BytesKept = $CumSize;
                    } else {
                        @a = stat("$CoreDir/$CoreFileName{$incarNum}");
                        local($lastTime) = $a[9];
                        local($currentTime) = time();
                        local($timeDiff) = 1+int(($currentTime - $lastTime)/(3600*24));
                        if( $timeDiff > $MaxDays ){ 
		            $cmd = "rm -f  $CoreDir/$CoreFileName{$incarNum}";
		            print "$cmd\n";
		            if (1 == $EraseTheFiles) {
			        system($cmd);
		            }
                            $BytesKept = $CumSize;
                        }
                    } 
            } else {
			$NumKeep++;
            }
	} else {
		$NumTrash++;
	}
	$CumSize += $size;
	printf "%5d %10d %8.3f %9.2f %10d %-s\n", $n, $CumSize, $CumSize/1024./1024., $weight, $size, $name;
	if ( 0 < $NumTrash ) { # trash 'em!
		# I could save all names and issue one command
		# The one command line may exceed shell limits
		# So, do'em one at a time; shouldn't be a big deal.
		$cmd = "rm -f  $dir/$name";
		print "$cmd\n";
		if (1 == $EraseTheFiles) {
			system($cmd);
		}
	}
}

printf "%5-s %10-s %8-s %9-s %10-s %-s\n", "n", "CumSize", "MB", "weight", "size", "name";

# Sort the incarnation numbers ordered by weight (priority), and
# keep those that fit under SizeLimit, and erase (trash) the rest,
# depending on the Erase|Print parameter.

foreach $record (sort ByWeight @Files) {
	($weight, $incar) = split(' ',$record);
	local($HasaCoreFile) = defined($CoreFileName{$incar});
	if (defined($LogFileName{$incar}) ) {
		&ProcessFile( $LogFileName{$incar}, $LogFileSize{$incar}, $HasaCoreFile, $LogDir, $incar );
	}
	if (defined($LongLogFileName{$incar}) ) {
		&ProcessFile( $LongLogFileName{$incar}, $LogFileSize{$incar}, $HasaCoreFile, $LogDir, $incar );
	}
	if (defined($CoreFileName{$incar}) ) {
		&ProcessFile( $CoreFileName{$incar}, $CoreFileSize{$incar}, $HasaCoreFile, $CoreDir, $incar );
	}
	if (defined($BakFileName{$incar}) ) {
		&ProcessFile( $BakFileName{$incar}, $BakFileSize{$incar}, $HasaCoreFile, $LogDir, $incar );
	}
	if (defined($LongBakFileName{$incar}) ) {
		&ProcessFile( $LongBakFileName{$incar}, $BakFileSize{$incar}, $HasaCoreFile, $LogDir, $incar );
	}
	if (defined($DefLFileName{$incar}) ) {
		&ProcessFile( $DefLFileName{$incar}, $DefLFileSize{$incar}, $HasaCoreFile, $DefaultLogDir, $incar );
	}
}

# Print out some stats on what we did

if (0 == $NumTrash) {
	$BytesKept = $CumSize;
}
$BytesTrashed = $CumSize - $BytesKept;
$MBtrashed = $BytesTrashed/1024./1024.;
printf "Keep %d logs, %d bytes, %.3f MB; Trash %d logs, %d bytes, %.3f MB.\n", $NumKeep, $BytesKept, $BytesKept/1024./1024., $NumTrash, $BytesTrashed, $MBtrashed;

$tlogs = $NumKeep + $NumTrash;
$tbytes = $BytesKept + $BytesTrashed;
printf "%d total logs, %d total bytes, %.3f total MB.\n", $tlogs, $tbytes, $tbytes/1024./1024.;

# $rc = int($NumTrash*256 + $MBtrashed);
# $rc = int($NumKeep*256*256 + $NumTrash*256 + $MBtrashed + .5);
# $rc = int($NumKeep*256*256 + $NumTrash*256 + $MBtrashed + .5);
$rc = $NumTrash;

printf "keep %d, trash %d, %d MB.  rc = %d, 0x%x\n", $NumKeep, $NumTrash, ($MBtrashed + .5), $rc, $rc;

exit $rc;
